#include "CSceneManager.h"
#include "WinDevice.h"
#include "IRenderDriver.h"
#include "CMeshCache.h"
#include "CCursorControl.h"
#include "CShaderManager.h"
#include "CLightsManager.h"
#include "IMeshLoader.h"
#include "IAnimatedMesh.h"
#include "CCameraSceneNode.h"
#include "CSceneNodeAnimatorCameraFPS.h"
#include "SAnimatedMesh.h"
#include "SMesh.h"
#include "SMeshBuffer.h"
#include "CCubeSceneNode.h"
#include "CSceneNodeAnimatorFlyCircle.h"


ldx::scene::CSceneManager::CSceneManager(device::WinDevice * device)
	:ISceneNode(NULL,this)
{
	Device = device;
	Device->grab();
	RenderDriver = device->getRenderDriver();
	RenderDriver->grab();
	CursorControl = Device->getCursorControl();
	CursorControl->grab();
	ShaderManager = RenderDriver->getShaderManager();
	ShaderManager->grab();
	LightsMgr = new CLightsManager(this);
	MeshCache = new CMeshCache();
	initLocalBuffers();
}

ldx::scene::CSceneManager::~CSceneManager()
{
	if (BufferCameraObj)
		delete BufferCameraObj;
	if (LightsMgr)
		LightsMgr->drop();
	for (uint i = 0; i < CameraList.size(); ++i)
	{
		CameraList[i]->drop();
	}
	CameraList.clear();
	/*for (uint i = 0; i < LightList.size(); ++i)
	{
		LightList[i]->drop();
	}
	LightList.clear();*/
	for (uint i = 0; i < SkyBoxList.size(); ++i)
	{
		SkyBoxList[i]->drop();
	}
	SkyBoxList.clear();
	for (uint i = 0; i < SolidNodeList.size(); ++i)
	{
		SolidNodeList[i]->drop();
	}
	SolidNodeList.clear();
	for (uint i = 0; i < MeshLoadersList.size(); ++i)
	{
		delete MeshLoadersList[i];
	}
	MeshLoadersList.clear();

	std::vector<IMeshLoader*> MeshLoadersList;
	if (MeshCache)
		delete MeshCache;
	if(Device)
		Device->grab();
	if(RenderDriver)
		RenderDriver->grab();
	if(CursorControl)
		CursorControl->grab();
	if(ShaderManager)
		ShaderManager->grab();
}

void ldx::scene::CSceneManager::drawAll()
{
	int i;
	//RenderDriver->setActiveProgram(scene::SMaterial());
	RenderDriver->setTransform(render::ETM_PROJECTION, mt::mat4(1));
	RenderDriver->setTransform(render::ETM_VIEW, mt::mat4(1));
	RenderDriver->setTransform(render::ETM_CAMERA, mt::mat4(1));
	//for (i = render::ETM_TRANSFORM_COUNT - 1; i >= video::ETS_TEXTURE_0; --i)
	//	RenderDriver->setTransform((video::E_TRANSFORMATION_STATE)i, mt::mat4(1));

	// do animations and other stuff.
	OnAnimate(Device->getRealDeltaTime());//to ms

	CamWorldPosition = mt::vec3f(0, 0, 0);
	if (CurrentCamera)
	{
		CurrentCamera->render();
		CamWorldPosition = CurrentCamera->getAbsolutePosition();
	}
	//update camera buffers and other global buffers.
	updateLocalBuffers();

	// let all nodes register themselves
	OnRegisterSceneNode();

	//render camera scenes
	{
		CurrentRendertime = ESNRP_CAMERA;

		for (i = 0; i<CameraList.size(); ++i)
			if (CurrentCamera != CameraList[i])
				CameraList[i]->render();
	}

	//RenderDriver->setActiveProgram(scene::SMaterial(E_MATERIAL_TYPE::EMT_SKY_BOX));
	for (uint j = 0; j < SkyBoxList.size(); ++j)
	{
		SkyBoxList[j]->render();
	}

	//RenderDriver->setActiveProgram(scene::SMaterial(E_MATERIAL_TYPE::EMT_SD_PHONG));
	for (uint j = 0; j < SolidNodeList.size(); ++j)
	{
		SolidNodeList[j]->update(Device->getRealDeltaTime());
		SolidNodeList[j]->render();
	}
}

ldx::scene::CLightsManager * ldx::scene::CSceneManager::getLightsManager()
{
	return LightsMgr;
}

ldx::render::IRenderDriver * ldx::scene::CSceneManager::getRenderDriver()
{
	return RenderDriver;
}

bool ldx::scene::CSceneManager::setActiveCamera(ICameraSceneNode * camera)
{
	if (camera)
		camera->grab();
	if (CurrentCamera)
		CurrentCamera->drop();

	CurrentCamera = camera;
	return true;
}

ldx::scene::ICameraSceneNode * ldx::scene::CSceneManager::getActiveCamera()
{
	return CurrentCamera;
}

uint ldx::scene::CSceneManager::registerNodeForRendering(ISceneNode * node, E_SCENE_NODE_RENDER_PASS pass)
{
	uint taken = 0;

	switch (pass)
	{
		// take camera if it is not already registered
	case ESNRP_CAMERA:
	{
		taken = 1;
		for (uint i = 0; i != CameraList.size(); ++i)
		{
			if (CameraList[i] == node)
			{
				taken = 0;
				break;
			}
		}
		if (taken)
		{
			CameraList.push_back(node);
		}
	}
	break;

	//case ESNRP_LIGHT:
	//	// TODO: Point Light culling..
	//	// Lighting model in lg has to be redone..
	//	//if (!isCulled(node))
	//{
	//	taken = 1;
	//	LightsMgr-
	//	for (uint i = 0; i != LightList.size(); ++i)
	//	{
	//		if (LightList[i] == node)
	//		{
	//			taken = 0;
	//			break;
	//		}
	//	}
	//	if (taken)
	//		LightList.push_back(node);
	//}
	//break;

	case ESNRP_SKY_BOX:
		taken = 1;
		for (uint i = 0; i != SkyBoxList.size(); ++i)
		{
			if (SkyBoxList[i] == node)
			{
				taken = 0;
				break;
			}
		}
		if (taken)
			SkyBoxList.push_back(node);
		break;
	case ESNRP_SOLID:
		//if (!isCulled(node))
	{
		taken = 1;
		for (uint i = 0; i != SolidNodeList.size(); ++i)
		{
			if (SolidNodeList[i] == node)
			{
				taken = 0;
				break;
			}
		}
		if (taken)
		{
			SolidNodeList.push_back(node);
		}
	}
	break;
	case ESNRP_AUTOMATIC:
		/*if (!isCulled(node))*/
	{
		const uint count = node->getMaterialCount();

		taken = 0;
		for (uint i = 0; i<count; ++i)
		{
			//if (rnd)
			{
				SolidNodeList.push_back(node);
				taken = 1;
				break;
			}
		}
	}
	break;

	case ESNRP_NONE: // ignore this one
		break;
	}
	return taken;
}

ldx::scene::IAnimatedMesh * ldx::scene::CSceneManager::getMesh(const std::string & filename)
{
	IAnimatedMesh* msh = MeshCache->getMeshByName(filename);
	if (msh)
		return msh;

	// iterate the list in reverse order so user-added loaders can override the built-in ones
	int count = static_cast<int>( MeshLoadersList.size());
	for (int i = count - 1; i >= 0; --i)
	{
		if (MeshLoadersList[i]->isALoadableFileExtension(filename))
		{
			// reset file to avoid side effects of previous calls to createMesh
			msh = MeshLoadersList[i]->createMesh(filename);
			if (msh)
			{
				MeshCache->addMesh(filename, msh);
				msh->drop();
				break;
			}
		}
	}


	if (!msh)
		std::cout << "Could not load mesh, file format seems to be unsupported " << filename << std::endl;
	else
		std::cout << "Loaded mesh :" << filename << std::endl;

	return msh;
}

bool ldx::scene::CSceneManager::addMesh(const std::string & name, IAnimatedMesh * mesh)
{
	if (MeshCache->isMeshLoaded(name))
	{
		std::cout << "Mesh named:" << name << " has existed!" << std::endl;
		return false;
	}
	MeshCache->addMesh(name, mesh);
	return true;
}

bool ldx::scene::CSceneManager::addMesh(const std::string & name, IMesh * mesh)
{
	if (MeshCache->isMeshLoaded(name))
	{
		std::cout << "Mesh named:" << name << " has existed!" << std::endl;
		return false;
	}
	SAnimatedMesh* animatedmesh = new SAnimatedMesh();
	animatedmesh->addMesh(mesh);
	MeshCache->addMesh(name, animatedmesh);
	animatedmesh->drop();
	return true;
}

bool ldx::scene::CSceneManager::addMesh(const std::string & name, IMeshBuffer * buffer)
{
	if (MeshCache->isMeshLoaded(name))
	{
		std::cout << "Mesh named:" << name << " has existed!" << std::endl;
		return false;
	}
	SAnimatedMesh* animatedmesh = new SAnimatedMesh();
	SMesh* mesh = new SMesh();
	mesh->addMeshBuffer(buffer);
	animatedmesh->addMesh(mesh);
	mesh->drop();
	MeshCache->addMesh(name, animatedmesh);
	animatedmesh->drop();
	return true;
}

ldx::scene::IMeshCache * ldx::scene::CSceneManager::getMeshCache()
{
	return MeshCache;
}

ldx::scene::ICameraSceneNode * ldx::scene::CSceneManager::addCameraSceneNode(
	ISceneNode * parent, const mt::vec3f & position, const mt::vec3f & lookat, int id, bool makeActive)
{
	if (!parent)
		parent = this;

	ICameraSceneNode* node = new CCameraSceneNode(parent, this, id, position, lookat);

	if (makeActive)
		setActiveCamera(node);
	node->drop();

	return node;
}

ldx::scene::ICameraSceneNode * ldx::scene::CSceneManager::addCameraSceneNodeFPS(ISceneNode * parent, float rotateSpeed, float moveSpeed, int id, SKeyMap * keyMapArray, int keyMapSize, bool noVerticalMovement, float jumpSpeed, bool invertMouseY, bool makeActive)
{
	ICameraSceneNode* node = addCameraSceneNode(parent, mt::vec3f(),
		mt::vec3f(0, 0, 100), id, makeActive);
	if (node)
	{
		ISceneNodeAnimator* anm = new CSceneNodeAnimatorCameraFPS(CursorControl,
			rotateSpeed, moveSpeed, jumpSpeed,
			keyMapArray, keyMapSize, noVerticalMovement, invertMouseY);

		// Bind the node's rotation to its target. This is consistent with 1.4.2 and below.
		node->bindTargetAndRotation(true);
		node->addAnimator(anm);
		anm->drop();
	}

	return node;
}

ldx::scene::ISceneNode * ldx::scene::CSceneManager::addModelSceneNode( std::string modelPath, ISceneNode * parent, int id, const mt::vec3f & position, const mt::vec3f & rotation, const mt::vec3f & scale)
{
	return nullptr;
}

ldx::scene::ISceneNode * ldx::scene::CSceneManager::addCubeSceneNode(ISceneNode * parent, int id, std::string tex, const mt::vec3f & position, const mt::vec3f & rotation, const mt::vec3f & scale)
{
	if (!parent)
		parent = this;
	CCubeSceneNode* cube = new CCubeSceneNode(parent, this, id, position, rotation, scale);
	if (tex != "")
		cube->setTexture(tex, 0);
	return cube;
}


ldx::scene::CLightSceneNode * ldx::scene::CSceneManager::addLight(E_LIGHT_MOVE_TYPE type, ISceneNode * parent, int id, mt::vec4f position, mt::vec3f intensities, float attenuation, float ambientCoefficient, float coneAngle, mt::vec3f coneDirection)
{
	if (!parent)
		parent = this;
	CLightSceneNode* light = LightsMgr->createLight(type, parent, id, position, intensities, attenuation, ambientCoefficient, coneAngle, coneDirection);
	return light;
}

ldx::scene::CLightSceneNode  * ldx::scene::CSceneManager::addSpotLight(E_LIGHT_MOVE_TYPE type, ISceneNode * parent, int id, mt::vec4f position, mt::vec3f intensities, float attenuation, float ambientCoefficient)
{
	if (!parent)
		parent = this;
	CLightSceneNode* light = LightsMgr->createSpotLightt(type, parent, id, position, intensities, attenuation, ambientCoefficient);

	return light;
}

ldx::scene::CLightSceneNode * ldx::scene::CSceneManager::addParalleLight(E_LIGHT_MOVE_TYPE type, ISceneNode * parent, int id, mt::vec4f position, mt::vec3f intensities, float ambientCoefficient)
{
	if (!parent)
		parent = this;
	CLightSceneNode* light = LightsMgr->createParalleLight(type, parent, id, position, intensities, ambientCoefficient);
	return light;
}

ldx::scene::CLightSceneNode * ldx::scene::CSceneManager::addFlashLight(E_LIGHT_MOVE_TYPE type, ISceneNode * parent, int id, mt::vec4f position, mt::vec3f intensities, float attenuation, float ambientCoefficient, float coneAngle, mt::vec3f coneDirection)
{
	if (!parent)
		parent = this;
	CLightSceneNode* light = LightsMgr->createFlashLight(type, parent, id, position, intensities, attenuation, ambientCoefficient, coneAngle, coneDirection);
	return light;
}

ldx::scene::ISceneNodeAnimator * ldx::scene::CSceneManager::createFlyCircleAnimator(const mt::vec3f & center, float radius, float speed, const mt::vec3f & direction, float startPosition, float radiusEllipsoid)
{
	ISceneNodeAnimator* anim = new CSceneNodeAnimatorFlyCircle(
		0.f, center,
		radius, speed, direction, radiusEllipsoid);
	return anim;
}

bool ldx::scene::CSceneManager::postEventFromUser(const SEvent & event)
{
	bool ret = false;
	ICameraSceneNode* cam = getActiveCamera();
	if (cam)
		ret = cam->OnEvent(event);
	return ret;
}

bool ldx::scene::CSceneManager::setGlobalConstantBuffers(uint types, mt::uint vsstartSlot, mt::uint psstartSlot, mt::uint gsstartSlot)
{
	SDeviceMap dm = RenderDriver->getDeviceMap();
	if (types & render::EST_VERTEXSHADER)
		dm.Window.D3DDeviceContext->VSSetConstantBuffers(vsstartSlot, 1, BufferCameraObj->getBufferObject());
	if (types & render::EST_PIXELSHADER)
		dm.Window.D3DDeviceContext->PSSetConstantBuffers(psstartSlot,1, BufferCameraObj->getBufferObject());
	if (types & render::EST_GEOMETRYSHADER)
		dm.Window.D3DDeviceContext->GSSetConstantBuffers(gsstartSlot,1, BufferCameraObj->getBufferObject());

	return true;
}

bool ldx::scene::CSceneManager::init()
{
	return false;
}

void ldx::scene::CSceneManager::initLocalBuffers()
{
	BufferCameraObj = new render::CBufferObject(RenderDriver->getDeviceMap(),
		D3D11_USAGE_DEFAULT,
		sizeof(render::CBufferCamera),D3D11_BIND_CONSTANT_BUFFER,
		0,0);
	BufferCameraObj->setDebugName("camerainfos_buffer");

}

void ldx::scene::CSceneManager::updateLocalBuffers()
{
	SDeviceMap dm = RenderDriver->getDeviceMap();
	BufferCamera.Camera = DirectX::XMMatrixTranspose(RenderDriver->getTransform(render::ETM_CAMERA).getXMMatrix());
	BufferCamera.View = DirectX::XMMatrixTranspose(RenderDriver->getTransform(render::ETM_VIEW).getXMMatrix());
	BufferCamera.Projection = DirectX::XMMatrixTranspose(RenderDriver->getTransform(render::ETM_PROJECTION).getXMMatrix());
	//BufferCamera.ViewDir = RenderDriver->getViewDirection();
	BufferCamera.ViewPos = RenderDriver->getViewPosition();
	//mt::mat4 mat = RenderDriver->getTransform(render::ETM_VIEW);
	//mat.makeInverse();
	//BufferCamera.InverseTransposedView = DirectX::XMMatrixTranspose(mat.getXMMatrix());*/
	//! update gpu buffer
	BufferCameraObj->updateSubBuffer(&BufferCamera, 0, 0, 0);
}


